﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WorldBankBBS.Common;
using WorldBankBBS.Comms;
using WorldBankBBS.Security;
using WorldBankBBS.Translators;

namespace WorldBankBBS.Logic
{
    public class Session : EventArgs, IDisposable, ISession
    {
        private Dictionary<string, object> _propertyBag = new Dictionary<string, object>();

        public Session(ICommunicator connection, IUser user)
        {
            Connection = connection;
            User = user;

            SessionUID = Guid.NewGuid();

            var session = new WorldBankBBS.Data.Session
            {
                SessionUID = SessionUID,
                StartedOn = DateTime.UtcNow,                
                Encoding = "Not Set",
                RemoteIP = Connection is ITcpConnection ? ((ITcpConnection)Connection).ClientAddress.ToString() : "Unknown",
            };

            var log = new Data.Log
            {
                LogUID = Guid.NewGuid(),
                Message = "Created Session",
                SessionUID = SessionUID,
                Timestamp = session.StartedOn,
                Type = "Information",
            };

            using (var entities = new Data.WorldBankBBSEntities())
            {
                entities.Sessions.Add(session);
                entities.Logs.Add(log);
                if (entities.GetValidationErrors().ToList().Count > 0)
                {
                    foreach(var error in entities.GetValidationErrors().ToList())
                    {
                        foreach (var valError in error.ValidationErrors)
                        {
                            System.Diagnostics.Debug.WriteLine(string.Format("{0}: {1}", valError.PropertyName, valError.ErrorMessage));
                        }
                    }
                }
                else
                {
                    entities.SaveChanges();
                }
            }
        }

        public ICommunicator Connection { get; set; }
        public IUser User { get; set; }

        public object this[string key]
        {
            get { return ContainsProperty(key) ? _propertyBag[key] : null; }
            set
            {
                lock(_propertyBag)
                    _propertyBag[key] = value;
            }
        }

        public bool ContainsProperty(string key)
        {
            lock(_propertyBag)
                return _propertyBag.ContainsKey(key);
        }

        public string Encoding
        {
            get { return _encoding; }
            set
            {
                _encoding = value;

                using (var entities = new Data.WorldBankBBSEntities())
                {
                    var session = entities.Sessions.FirstOrDefault(
                        s => s.SessionUID == SessionUID);

                    if (session != null)
                    {
                        session.Encoding = value;

                        entities.SaveChanges();
                    }
                }
            }
        }

        public Guid SessionUID { get; private set; }

        public byte[] GetValue(string token)
        {
            switch (token.ToLower())
            {
                case "{$encoding}":
                    return ASCIIEncoding.ASCII.GetBytes(Encoding);
                case "{$head_top}":
                    return HeadTop;
                case "{$head_mid}":
                    return HeadMid;
                case "{$head_bot}":
                    return HeadBot;
                case "{$head_join}":
                    return HeadJoin;
                case "{$client_ip}":
                    if (Connection is ITcpConnection)
                    {
                        return ASCIIEncoding.ASCII.GetBytes(((ITcpConnection) Connection).ClientAddress.ToString());
                    }
                    break;
                case "{$server_date}":
                    return ASCIIEncoding.ASCII.GetBytes(DateTime.Today.ToShortDateString());
                case "{$server_time}":
                    return ASCIIEncoding.ASCII.GetBytes(DateTime.Now.ToShortTimeString());
                case "{$username}":
                    if (User != null)
                        return GetString(((Data.User) User).UserName);

                    return ASCIIEncoding.ASCII.GetBytes("Not Logged In!");
                case "{$alias}":
                    if (User != null)
                        return GetString(((Data.User)User).Alias);

                    return ASCIIEncoding.ASCII.GetBytes("Not Logged In!");
                case "{$pause}":
                    InvokePause(Encoding);
                    return new byte[] {};
            }

            if (ContainsProperty(token.Substring(2, token.Length - 3)))
                return ASCIIEncoding.ASCII.GetBytes(this[token.Substring(2, token.Length - 3)].ToString());

            return ASCIIEncoding.ASCII.GetBytes(token);
        }

        private byte[] GetResource(string name)
        {
            var b = new byte[1024];
            switch (Encoding)
            {
                case "PETSCII":
                    var cbm = new CbmTranslator() { CurrentSession = this };
                    cbm.ResourceToBytes(b, name);
                    return b;

                case "ANSI":
                    var ansi = new AnsiTranslator() { CurrentSession = this };
                    ansi.ResourceToBytes(b, name);
                    return b;

                default:
                    var ascii = new AsciiTranslator() { CurrentSession = this };
                    ascii.ResourceToBytes(b, name);
                    return b;
            }
        }

        private byte[] GetString(string token)
        {
            var b = new byte[1024];
            switch (Encoding)
            {
                case "PETSCII":
                    var cbm = new CbmTranslator() { CurrentSession = this };
                    cbm.StringToBytes(b, token);
                    return b;

                case "ANSI":
                    var ansi = new AnsiTranslator() { CurrentSession = this };
                    ansi.StringToBytes(b, token);
                    return b;

                default:
                    var ascii = new AsciiTranslator() { CurrentSession = this };
                    ascii.StringToBytes(b, token);
                    return b;
            }
        }

        private byte[] HeadTop
        {
            get { return GetResource(((int?)this["ScreenWidth"] ?? 40) <= 40 ? "HEAD_TOP40" : "HEAD_TOP80"); }
        }
        private byte[] HeadBot
        {
            get { return GetResource(((int?)this["ScreenWidth"] ?? 40) <= 40 ? "HEAD_BOT40" : "HEAD_BOT80"); }
        }
        private byte[] HeadJoin
        {
            get { return GetResource(((int?)this["ScreenWidth"] ?? 40) <= 40 ? "HEAD_JOIN40" : "HEAD_JOIN80"); }
        }
        private byte[] HeadMid
        {
            get { return GetResource("HEAD_MID"); }
        }

        public bool GuestLoginCompleted { get; set; }
        public bool LoginCompleted { get; set; }

        private bool _isDisposed;
        private string _encoding;

        public void Dispose()
        {
            if (!_isDisposed)
            {
                _isDisposed = true;

                using (var entities = new Data.WorldBankBBSEntities())
                {
                    var session = entities.Sessions.FirstOrDefault(
                        s => s.SessionUID == SessionUID);

                    if (session != null)
                    {
                        session.EndedOn = DateTime.UtcNow;

                        var log = new Data.Log
                        {
                            LogUID = Guid.NewGuid(),
                            Message = "Ending Session",
                            SessionUID = SessionUID,
                            Timestamp = session.EndedOn.Value,
                            Type = "Information"
                        };

                        entities.Logs.Add(log);

                        entities.SaveChanges();
                    }
                }
            }
        }

        public event EventHandler<Session> Pause;
        public bool InvokePause(string encoding)
        {
            if (Pause != null)
            {
                //var e = new BoolEventArgs() { Encoding = encoding, Value = false, Session = this };
                Pause(this, this);
                return (bool)this["PauseAborted"];
            }

            return false;
        }

        public string StripString(string text)
        {
            if (text == null) throw new ArgumentNullException("text");

            var result = new StringBuilder();

            var inTag = false;

            foreach (var c in text)
            {
                if (c == '{' && !inTag)
                {
                    inTag = true;
                }
                else if (c == '}' && inTag)
                {
                    inTag = false;
                }
                else if(!inTag)
                {
                    result.Append(c);
                }
            }

            return result.ToString();
        }
    }
}
